/*
 * Expression.java
 *
 * Created on June 25, 2007, 9:44 PM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package tal.drivechain.expression;
/**
 * A Class for evaluating mathematical expressions and using them inside the chain environment.
 * For expression parsing and evaluation the JEP java library is used.
 *
 * Expressions may contain string parts and mathematical expression parts. A mathematical expression
 * must be surrounded by brackets. For example the expression string "frog{2*5}+10" will evaluate to
 * a String of frog10+10. 
 * Expression syntax is the same as the JEP expression syntax with standard constants and functions 
 * with the following additions and changes:
 * power - instead of ^ use ** on the command line (patches may contain both symbols)
 * boolean operators -  Instead of || use ~~, instead of > use ++ and instead of < use -- (both on the command line and in patch files).
 *                      instead of && use \\.
 * strings - instead of "string" use 'string' (also only for command line args)
 * time - to use a time value such as 200hz surround it with :: - for example :200hz: :2secs: or :2yrs+30days:
 * note names are entered using @note_name@ for example @a#-2@
 *
 * For more information about the JEP library from Singular Systems see http://www.singularsys.com/jep/
 *
 * @author Tal Eisenberg
 *//*
public class OldExpression {
    private XJep jep;
    private Node[] nodes;
    private String[] notExpressions;
    private boolean complexExpr;
    private DataType returnType;
    private boolean hasVars = false;
    private static final Pattern notePattern = Pattern.compile("\\@[^@]*\\@");
    private static final Pattern unitsPattern = Pattern.compile("\\:[^:]*\\:");
    private boolean requireStringNests = true;
    private Hashtable<String, VarTableAction> getVars = new Hashtable<String, VarTableAction>();
    private GetProperty propVal = new GetProperty();

    /** 
     * Creates a new instance of Expression with the expression supplied as the
     * parameter.
     *
     * @param expr  The expression string that the Expression object will hold.
     * @throws IllegalArgumentException If the String can't be parsed as an expression. 
     *//*
    public OldExpression(String expr, DataType returnType, boolean requireStringNests) throws IllegalArgumentException{
        this.returnType = returnType;
        this.requireStringNests = requireStringNests;
        
        // 1st stage of parsing: parsing jep expressions:
        jep = new XJep();
        jep.addStandardConstants();
        jep.addStandardFunctions();
        addDriveChainFunctions();
        addCustomFunctions();
        jep.addFunction("and", new BitwiseAnd());
        jep.addFunction("or", new BitwiseOr());
        jep.addFunction("xor", new BitwiseXor());
        jep.addFunction("not", new BitwiseNot());
        jep.addFunction("leftShift", new LeftShift());
        jep.addFunction("rightShift", new RightShift());
        jep.addFunction("indexOf", new IndexOf());
        jep.addFunction("getProp", propVal);
        jep.getSymbolTable().addConstant("true", 1);
        jep.getSymbolTable().addConstant("false", 0);
        jep.setAllowUndeclared(true);
        try {
            parseExpressions(expr);
            // 2nd stage of parsing: simplifying nodes:
            simplifyNodes();
        } catch (ParseException pe) {
            throw new IllegalArgumentException(pe.getMessage() + " (" + expr + ")");
        }
        // check if this is a complex expression in the format of xxx{yyy}zzz{qqq}...
        if (nodes.length<2 && notExpressions.length<2 && notExpressions[0].length()==0)
            complexExpr = false;
        else complexExpr = true;

        // 3rd stage: check if the expression need vars to evaluate
        SymbolTable vartable = jep.getSymbolTable();
        Enumeration<String> varnames = vartable.keys();
        while (varnames.hasMoreElements()) {
            String name = varnames.nextElement();
            if (!name.equals("e") && !name.equals("pi") && !name.equals("true") && !name.equals("false")) {
                String realname = restoreArrayElements(name);
                
                VarTableAction getAction = VarTableAction.parseAction(VarTableAction.Action.GET, realname);
                getVars.put(name, getAction);
                hasVars = true; 
            }
        }
    }

    public OldExpression(String element, DataType returnType) {
        this(element, returnType, true);
        
    }

    public void addFunction(String name, PostfixMathCommand func) {
        jep.addFunction(name, func);
    }

    /**
     * If a type is set using this method, Expression will try to return it's value after
     * evaluation in a class that can be used to make the desired {@link Variable} value.
     *
     * @param type  any Variable TYPE constant (such as Variable.TYPE_INT, Variable.TYPE_MIDI, etc.).
     *//*
    public void setValueVarType(DataType type) {
        this.returnType = type;
    }
    
    /**
     * Returns the desired return Variable DataType.
     *//*
    public DataType getReturnVarType() {
        return returnType;
    }
    
    /**
     * The same as Expression.evaluate(null)
     *//*
    public Object evaluate() throws IllegalArgumentException {
        return evaluate(null);
    }
    
    /**
     * Evaluates the Expression and returns the final value. If setValueVarType() and
     * setIsArray() were not called the returned Object class will be determined by
     * the JEP class. If a value var type was set the method will try to return an
     * object that could be used as value for the required var type.
     *
     * @param localVars This {@link VarTable} will be used when looking for variable 
     *                  values that are needed for evaluating the expression.
     * @throws IllegalArgumentException If an error is encountered while evaluating
     *                                  or if the returned value can't be cast to
     *                                  the required variable data type.
     *//*
    public Object evaluate(EvaluationEnvironment env) throws IllegalArgumentException {
        setEnv(env);
        
        try {
            if (hasVars) addVars(env);
            if (!complexExpr) {
                Object o = jep.evaluate(nodes[0]);
                if (returnType!=null && !returnType.checkType(o)) return returnType.cast(o);
                return o;
            } else {
                String out = "";
                for (int i=0; i<nodes.length; i++) {
                    out += notExpressions[i] + jep.evaluate(nodes[i]);
                }
                if (notExpressions.length>nodes.length) 
                    out+=notExpressions[notExpressions.length-1];
                out = ParsingAid.parseEscapeCharacters(out);
                if (returnType!=null && !returnType.checkType(out)) 
                    throw new IllegalArgumentException("Can't cast expression result (string) into " +returnType.getName());
                else return out;
            }
        } catch (ParseException pe) {
            String err = pe.getMessage() + (pe.currentToken!=null?": " + pe.currentToken.toString():"");
            err = restoreArrayElements(err);
            if (err.endsWith("no equation."))
                err = err.substring(0, err.indexOf(" no equation.")) + ".";
            throw new IllegalArgumentException(err);
        }
    }

    private void addCustomFunctions() {
    }

    private void addDriveChainFunctions() {
        BitwiseAnd and = new BitwiseAnd(); BitwiseNot not = new BitwiseNot(); BitwiseOr or = new BitwiseOr(); BitwiseXor xor = new BitwiseXor();
        IndexOf indexOf = new IndexOf(); LeftShift leftShift = new LeftShift(); RightShift rightShift = new RightShift();

        // math functions.
        jep.addFunction(and.getName(), and);
        jep.addFunction(not.getName(), not);
        jep.addFunction(or.getName(), or);
        jep.addFunction(xor.getName(), xor);
        jep.addFunction(indexOf.getName(), indexOf);
        jep.addFunction(leftShift.getName(), leftShift);
        jep.addFunction(rightShift.getName(), rightShift);

        // env functions.
        jep.addFunction(propVal.getName(), propVal);
    }
    
    private void addVars(EvaluationEnvironment env) throws ParseException {
        SymbolTable vartable = jep.getSymbolTable();
        vartable.clearValues();        
        Enumeration<String> varnames = vartable.keys();
        while (varnames.hasMoreElements()) {
            String name = varnames.nextElement();
            Variable value = null;
            if (!name.equals("e") && !name.equals("pi") && !name.equals("false") && !name.equals("true")) {
                value = getVars.get(name).get(env);
            }

            if (value!=null && (value.getDataType().getClass().equals(ExprType.class))) {
                value = parseFunc(value, env);
            }
            
            if (value!=null) {
                jep.setVarValue(name, value.getValue(env));
            }
            
        }
    }

    private Node makeNode(String expression) throws ParseException {
        try {
            expression = parseNotes(expression);
            expression = parseUnits(expression);
        } catch (IllegalArgumentException e) {
            throw new ParseException(e.getMessage());
        }

        expression = expression.replaceAll("\\*\\*", "^");
        expression = expression.replaceAll("~", "|");
        expression = expression.replaceAll("\\\\", "&");            
        expression = expression.replaceAll("#", "_prop_");
        expression = expression.replaceAll("\\+\\+", ">");
        expression = expression.replaceAll("--", "<");
        expression = expression.replaceAll("'", "\"");
        expression = parseArrayElements(expression);
        try {
            Node ret = jep.parse(expression);
            return ret;
        } catch (org.nfunk.jep.TokenMgrError e) {
            throw new ParseException(e.getMessage());
        }

    }

    private Variable parseFunc(Variable funcVar, EvaluationEnvironment env) {
        Object ret = funcVar.getValue(env);
        return new Variable(funcVar.getName(), ((SimpleExpression)funcVar.getInternalValue()).getExprReturnType(), ret);
    }
    
    private String parseNotes(String expr) throws IllegalArgumentException {
        StringBuffer b = new StringBuffer();
        Matcher m = notePattern.matcher(expr);
        while (m.find()) {
            m.appendReplacement(b, ""+MidiUtility.stringToNoteNumber(expr.substring(m.start()+1, m.end()-1)));
        }
        m.appendTail(b);
        return b.toString();                
    }
    
    private String parseUnits(String expr) throws IllegalArgumentException {
        StringBuffer b = new StringBuffer();
        Matcher m = unitsPattern.matcher(expr);
        while (m.find()) {
            m.appendReplacement(b, ""+UnitParser.parse(expr.substring(m.start()+1, m.end()-1)));
        }
        m.appendTail(b);
        return b.toString();        
    }
    
    private void parseExpressions(String source) throws ParseException {
        if (source==null || source.equals("")) throw new ParseException("Can't parse null expression.");        
        if (returnType!=null && returnType.matches(DataType.STRING) && !source.startsWith("{") && !source.startsWith("}")) 
            parseStringExpressions(source);
        else {
            if (source.startsWith("{") && source.endsWith("}")) {
                notExpressions = new String[] { "" };
                nodes = new Node[1];
                nodes[0] = makeNode(source.substring(1, source.length()-1));
            } else throw new ParseException("Missing opening or closing brackets {}: " + source);
        }
    }
    
    private void parseStringExpressions(String source) throws ParseException {
        if (requireStringNests) {
            if ((source.startsWith("'") && source.endsWith("'")) ||
                    (source.startsWith("/") && source.endsWith("/")))
                source = source.substring(1, source.length()-1);
            else throw new ParseException("Missing opening or closing ' symbol (" + source + ").");
        }
        
        if (source.length()==0) source = " ";
        List<Node> vNodes = new ArrayList<Node>();
        List<String> stringParts = new ArrayList<String>();
        int curidx = 0;
        int lastStringEnd = 0;
        while(curidx<source.length()) {
            if (source.charAt(curidx)=='{') {
                if (!ParsingAid.isNested(source, curidx, "'\"/", "'\"/")) {
                    //get balanced
                    int bidx = Tokenizer.balanced(source, curidx, '{', '}');
                    if (bidx==-1) 
                        throw new ParseException("Missing closing bracket (}): \"" + source +"\"");
                    String expression = source.substring(curidx+1, bidx);
                    vNodes.add(makeNode(expression));
                    stringParts.add(source.substring(lastStringEnd, curidx));
                    lastStringEnd = bidx+1;
                    
                    curidx = bidx;
                }
            }
            curidx++;
        }
        
        if (lastStringEnd<source.length()) stringParts.add(source.substring(lastStringEnd));
        notExpressions = stringParts.toArray(new String[0]);
        nodes = vNodes.toArray(new Node[0]);
    }

    private void setEnv(EvaluationEnvironment env) {
        propVal.setEnv(env);
    }
    
    private void simplifyNodes() throws ParseException {
        for (Node node : nodes) {
            node = jep.preprocess(node);            
            node = jep.simplify(node);
        }
    }

    private static String parseArrayElements(String source) {
        //find every balanced area of [...]
        String ret = "";
        for (int i=0; i<source.length(); i++) {
            if (source.charAt(i)=='[') {
                int balancing = Tokenizer.balanced(source, i, '[', ']');
                if (balancing == -1)
                    throw new IllegalArgumentException("Missing closing bracket in expression: ..." + source.substring(i));
                String sub = source.substring(i, balancing+1);
                sub = sub.replaceAll("\\[", "_start_bracket_").replaceAll("\\]", "_end_bracket_").replaceAll("\\,", "_comma_");
                sub = sub.replaceAll("\\;", "_comdot_").replaceAll("\\.", "_dot_").replaceAll("\\{", "_start_curly");
                sub = sub.replaceAll("\\}", "_end_curly_").replaceAll("\\(", "_start_paren_").replaceAll("\\)" , "_end_paren_").replaceAll("\\'", "_apos_");
                ret = ret + sub;
                i = balancing;
            } else {
                ret = ret + source.charAt(i);
            }
        }
        return ret;
    }

    private static String restoreArrayElements(String source) {
        String ret = source.replaceAll("_start_bracket_", "[").replaceAll("_end_bracket_", "]").replaceAll("_comma_", ",")
                    .replaceAll("_comdot_", ";").replaceAll("_dot_", ".").replaceAll("_start_curly", "{")
                    .replaceAll("_end_curly_", "}").replaceAll("_start_paren_", "(").replaceAll("_end_paren_", ")").replaceAll("_apos_", "'")
                    .replaceAll("_prop_", "#");
        
        return ret;
    }


    @Override
    public String toString() {
        String ret = "";
        for (int i=0; i<nodes.length; i++) {
            ret+=notExpressions[i]+ "{" + restoreArrayElements(jep.getPrintVisitor().toString(nodes[i])) + "}";
        }
        if (nodes.length<notExpressions.length)
            ret+=notExpressions[notExpressions.length-1];
        if (this.requireStringNests && returnType.matches(DataType.STRING)) {
            if (ret.startsWith("'") && ret.endsWith("'")) ret = ret.substring(1, ret.length()-1);
            return "'" + ret + "'";
        } else return ret;
        
    }
}*/